﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Threading;
using ZOIL.Framework.Presentation;

namespace ZOIL.DataModel
{
    public class ZLandscapeCanvas : Canvas
    {
        #region private fields

        private readonly List<Object> _nonPersistableItems;
        private INotifyCollectionChanged _observableCollection;
        private IPersistsVisualProperties _persistsVisualPropertiesCollection;

        #endregion

        #region constructor

        public ZLandscapeCanvas()
        {
            _nonPersistableItems = new List<object>();
        }

        #endregion

        #region events

        #region Delegates

        public delegate void ZLandscapeCanvasEventHandler(object sender, ZLandscapeCanvasEventArgs args);

        #endregion

        public static readonly RoutedEvent ElementAddedEvent = EventManager.RegisterRoutedEvent("ElementAdded", RoutingStrategy.Bubble,
                                                                                                typeof (ZLandscapeCanvasEventHandler),
                                                                                                typeof (ZLandscapeCanvas));

        public static readonly RoutedEvent ElementRemovedEvent = EventManager.RegisterRoutedEvent("ElementRemoved", RoutingStrategy.Bubble,
                                                                                                  typeof (ZLandscapeCanvasEventHandler),
                                                                                                  typeof (ZLandscapeCanvas));

        public event ZLandscapeCanvasEventHandler ElementAdded
        {
            add { AddHandler(ElementAddedEvent, value); }
            remove { RemoveHandler(ElementAddedEvent, value); }
        }

        public event ZLandscapeCanvasEventHandler ElementRemoved
        {
            add { AddHandler(ElementRemovedEvent, value); }
            remove { RemoveHandler(ElementRemovedEvent, value); }
        }

        #endregion

        #region attached properties

        #region OnElementAdded

        public static DependencyProperty OnElementAddedProperty = DependencyProperty.RegisterAttached("OnElementAdded",
                                                                                                      typeof (RoutedCommand),
                                                                                                      typeof (ZLandscapeCanvas),
                                                                                                      new FrameworkPropertyMetadata(null,
                                                                                                                                    OnElementAddedChanged));

        private static void OnElementAddedChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
        {
            HandleChange(e.OldValue, e.NewValue, target as UIElement, ElementAddedEvent, new ZLandscapeCanvasEventHandler(OnElementAdded));
        }

        private static void OnElementAdded(object sender, ZLandscapeCanvasEventArgs args)
        {
            ExecuteCommand(sender as UIElement, OnElementAddedProperty, args);
        }

        #endregion

        #region OnElementRemoved

        public static DependencyProperty OnElementRemovedProperty = DependencyProperty.RegisterAttached("OnElementRemoved",
                                                                                                        typeof (RoutedCommand),
                                                                                                        typeof (ZLandscapeCanvas),
                                                                                                        new FrameworkPropertyMetadata(null,
                                                                                                                                      OnElementRemovedChanged));

        private static void OnElementRemovedChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
        {
            HandleChange(e.OldValue, e.NewValue, target as UIElement, ElementRemovedEvent, new ZLandscapeCanvasEventHandler(OnElementRemoved));
        }

        private static void OnElementRemoved(object sender, ZLandscapeCanvasEventArgs args)
        {
            ExecuteCommand(sender as UIElement, OnElementRemovedProperty, args);
        }

        #endregion

        private static void HandleChange(object OldValue, object NewValue, UIElement element, RoutedEvent targetEvent, Delegate handler)
        {
            if (element != null)
            {
                // If we're putting in a new command and there wasn't one already hook the event
                if ((NewValue != null) && (OldValue == null || !(bool) OldValue))
                {
                    element.AddHandler(targetEvent, handler);
                }
                    // If we're clearing the command and it wasn't already null unhook the event
                else if ((NewValue == null || !(bool) NewValue) && (OldValue != null))
                {
                    element.RemoveHandler(targetEvent, handler);
                }
            }
        }

        private static void ExecuteCommand(UIElement sender, DependencyProperty commandProperty, Object parameter)
        {
            var command = (RoutedCommand) sender.GetValue(commandProperty);
            command.Execute(parameter, sender);
        }

        #endregion

        #region private methods

        private void AddItems()
        {
            foreach(Tuple<IPersistable, IVisualProperties> t in ItemsSource)
            {
                AddItem(t.Item1, t.Item2);
            }
        }

        private void AddItem(IPersistable item, IVisualProperties prop)
        {
            FrameworkElement fe = MVVMUtil.CreateIndexedView(item, prop.ViewIndex);
            if(fe != null)
            {
                base.Children.Add(fe);

                SetViewBindings(fe, prop);

                //Raise ElementAdded Event here
                var args = new ZLandscapeCanvasEventArgs();
                args.View = fe;
                args.Model = item;
                args.RoutedEvent = ElementAddedEvent;

                //fe.RaiseEvent(args);
                Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => fe.RaiseEvent(args)));
            }
            else
            {
                Console.WriteLine("No view found for Model Type: " + item.GetType());
            }
        }

        /*[Obsolete("Use AddItem(IPersistable, IVisualProperties) instead")]
        private void AddItem(IPersistable item)
        {
            var prop = _persistsVisualPropertiesCollection.GetVisualProperties(item.DatabaseId);
            
            FrameworkElement fe = MVVMUtil.CreateIndexedView(item, prop.ViewIndex);
            if (fe != null)
            {
                base.Children.Add(fe);

                SetViewBindings(fe, prop);

                //Raise ElementAdded Event here
                var args = new ZLandscapeCanvasEventArgs();
                args.View = fe;
                args.Model = item;
                args.RoutedEvent = ElementAddedEvent;
                //fe.RaiseEvent(args);
                Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => { fe.RaiseEvent(args); }));
            }
            else
            {
                Console.WriteLine("No view found for Model Type: " + item.GetType());
            }
        }*/

        private void SetBinding(DependencyProperty property, String targetProperty, String sourceProperty, FrameworkElement fe, IVisualProperties prop)
        {
            SetBinding(property, targetProperty, sourceProperty, fe, prop, null);
        }

        private void SetBinding(DependencyProperty property, String targetProperty, String sourceProperty, FrameworkElement fe, IVisualProperties prop,
                                IValueConverter converter)
        {
            var b = new Binding(targetProperty);
            b.Path = new PropertyPath(sourceProperty);
            b.Source = prop;
            b.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
            b.Mode = BindingMode.TwoWay;

            if (converter != null)
            {
                b.Converter = converter;
            }

            fe.SetBinding(property, b);
        }

        private void SetViewBindings(FrameworkElement fe, IVisualProperties prop)
        {
            SetBinding(LeftProperty, "Left", "X", fe, prop);
            SetBinding(TopProperty, "Top", "Y", fe, prop);
            SetBinding(WidthProperty, "Width", "Width", fe, prop);
            SetBinding(HeightProperty, "Height", "Height", fe, prop);
            SetBinding(OpacityProperty, "Opacity", "Opacity", fe, prop);
            SetBinding(ZIndexProperty, "ZIndex", "ZIndex", fe, prop);

            var comp = fe as ZComponent;
            if (comp != null)
            {
                SetBinding(ZComponent.AngleProperty, "Angle", "Angle", comp, prop);
                SetBinding(Control.TemplateProperty, "Template", "TemplateId", comp, prop, new StringToTemplateConverter(comp));
            }
        }

        #endregion

        #region public methods

        public void AddNonPersistableItem(Object o, IVisualProperties prop)
        {
            var element = MVVMUtil.CreateIndexedView(o, prop.ViewIndex);
            if (element != null)
            {
                SetViewBindings(element, prop);
                Children.Add(element);
                _nonPersistableItems.Add(o);
            }
        }

        public void RemoveNonPersistableItem(Object o)
        {
            IEnumerable<FrameworkElement> matches = from UIElement elem in Children
                                                    let fe = elem as FrameworkElement
                                                    where fe != null
                                                    let vm = fe.DataContext as IViewModel
                                                    where vm != null && o == vm.Model
                                                    select fe;

            foreach (FrameworkElement match in matches.ToArray())
            {
                Children.Remove(match);
            }
            _nonPersistableItems.Remove(o);
        }

        #endregion

        #region dp

        #region ItemsSource

        // Using a DependencyProperty as the backing store for Items.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof (IEnumerable),
                                                                                                    typeof (ZLandscapeCanvas),
                                                                                                    new FrameworkPropertyMetadata(null,
                                                                                                                                  OnItemsSourceChanged));

        public IEnumerable ItemsSource
        {
            get { return (IEnumerable) GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }

        private static void OnItemsSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            var cnv = obj as ZLandscapeCanvas;
            //if the new itemssource is not null, assign the values and attach an event handler to the collection changed event
            if ((e.NewValue != null) && (e.OldValue == null))
            {
                if (e.NewValue is INotifyPropertyChanged)
                {
                    (e.NewValue as INotifyPropertyChanged).PropertyChanged += cnv.OnItemsSourcePropertyChanged;
                }

                cnv._observableCollection = e.NewValue as INotifyCollectionChanged;
                cnv._persistsVisualPropertiesCollection = e.NewValue as IPersistsVisualProperties;

                if (cnv._observableCollection != null)
                {
                    cnv._observableCollection.CollectionChanged += cnv.OnItemsSourceCollectionChanged;
                }

                if (cnv._persistsVisualPropertiesCollection != null)
                {
                    cnv.AddItems();
                }
            }
            else if ((e.NewValue == null) && (e.OldValue != null))
            {
            }
        }

        #endregion

        #endregion

        #region event handling

        private void OnItemsSourcePropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            //Brauchts gar nicht da in CollectionChanged abgefangen

            /*
            if (e.PropertyName == "OnRemoteUpdate")
            {
                //MONTAGSLÖSUNG
                Dispatcher.BeginInvoke(new Action(delegate
                    {
                        IEnumerable<IPersistable> objectsOfRootCollection = from UIElement elem in Children
                                                                            let fe = elem as FrameworkElement
                                                                            where fe != null
                                                                            let vm = fe.DataContext as IViewModel
                                                                            where vm != null
                                                                            let mdl = vm.Model as IPersistable
                                                                            where
                                                                                mdl != null && !_nonPersistableItems.Contains(mdl) &&
                                                                                mdl != _persistsVisualPropertiesCollection
                                                                            select mdl;

                        IPersistable[] cnvMembers = objectsOfRootCollection.ToArray();

                        IPersistable[] rootMembers = (sender as ICollection).OfType<IPersistable>().ToArray();

                        IPersistable[] newMembers = rootMembers.Except(cnvMembers).ToArray();
                        IPersistable[] removedMembers = cnvMembers.Except(rootMembers).ToArray();

                        foreach (IPersistable item in removedMembers)
                        {
                            IEnumerable<FrameworkElement> matches = from UIElement elem in Children
                                                                    let fe = elem as FrameworkElement
                                                                    where fe != null
                                                                    let vm = fe.DataContext as IViewModel
                                                                    where vm != null && item == vm.Model
                                                                    select fe;

                            foreach (FrameworkElement match in matches.ToArray())
                            {
                                Children.Remove(match);
                                //Raise ElementRemoved Event here
                                var args = new ZLandscapeCanvasEventArgs();
                                                                                 {
                                                                                     View = match,
                                                                                     RoutedEvent = ElementRemovedEvent
                                                                                 };
                                match.RaiseEvent(args);
                            }
                        }

                                                          foreach (var item in newMembers)
                        {
                            AddItem(item);
                        }
                    }), DispatcherPriority.Background);
            }
            */
        }

        private void OnItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            //hier rein kommen sowohl locale als auch remote Änderungen, werden aber gleich behandelt da nur an Änderung von Collection interessiert
            switch(e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    {
                        foreach(Tuple<IPersistable, IVisualProperties> t in e.NewItems)
                        {
                            AddItem(t.Item1, t.Item2);
                        }
                    }   
                    break;
                case NotifyCollectionChangedAction.Remove:
                    {
                        var matches = from fe in Children.OfType<FrameworkElement>()
                                      let vm = fe.DataContext as IViewModel
                                      where
                                          vm != null &&
                                          vm.Model is IPersistable &&
                                          e.OldItems.Cast<Tuple<IPersistable, IVisualProperties>>()
                                           .Any(t => t.Item1.DatabaseId == ((IPersistable) vm.Model).DatabaseId)
                                      select fe;

                        var es = matches.ToArray();
                        foreach(var match  in es)
                        {
                            Children.Remove(match);
                            //Rause ElementAdded Event here
                            var args = new ZLandscapeCanvasEventArgs();
                            args.View = match;
                            args.Model = (match.DataContext as IViewModel).Model as IPersistable;
                            args.RoutedEvent = ElementRemovedEvent;
                            match.RaiseEvent(args);

                            //raise the event again, since the unconnected element will not pass it up in the visual tree
                            RaiseEvent(args);
                        }
                    }
                    break;
                case NotifyCollectionChangedAction.Reset:
                    {
                        Children.Clear();
                    }
                    break;
            }

            /*switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    foreach (var i in e.NewItems)
                    {
                        IPersistable item = ((Tuple<IPersistable, IVisualProperties>)i).Item1;
                        AddItem(item);
                    }
                    break;
                case NotifyCollectionChangedAction.Remove:
                    IEnumerable<FrameworkElement> matches = from UIElement elem in Children
                                                            let fe = elem as FrameworkElement
                                                            where fe != null
                                                            let vm = fe.DataContext as IViewModel
                                                            where vm != null && e.OldItems.Contains(vm.Model)
                                                            select fe;

                    foreach (var match in matches.ToArray())
                    {
                        Children.Remove(match);
                        //Raise ElementAdded Event here
                        var args = new ZLandscapeCanvasEventArgs
                                       {
                                           View = match,
                                           Model = (match.DataContext as IViewModel).Model as IPersistable,
                                           RoutedEvent = ElementRemovedEvent
                                       };
                        match.RaiseEvent(args);

                        //raise the event again, since the unconnected element will not pass it up in the visual tree
                        RaiseEvent(args);
                    }
                    break;
                case NotifyCollectionChangedAction.Reset:
                    //completely delete all elements !!perhaps this is too drastic, if we want to have other elements in the canvas too!!
                    Children.Clear();
                    //foreach(UIElement elem in Children)
                    //{
                    //    FrameworkElement fe = elem as FrameworkElement;
                    //    if(fe != null)
                    //    {
                    //        BaseViewModel bvm = fe.DataContext as BaseViewModel;
                    //        if(bvm != null)
                    //        {
                    //            if(e.OldItems.Contains(bvm.Model))
                    //            {
                    //                Children.Remove(elem);
                    //            }
                    //        }
                    //    }
                    //}
                    break;
            }*/
        }

        #endregion
    }

    public class ZLandscapeCanvasEventArgs : RoutedEventArgs
    {
        public FrameworkElement View { get; set; }
        public IPersistable Model { get; set; }
    }

    #region StringToTemplateConverter

    /// <summary>
    /// Loads the template with the given template name (see Convert). This is used to customize user controls.
    /// </summary>
    public class StringToTemplateConverter : IValueConverter
    {
        private readonly ZComponent _component;

        public StringToTemplateConverter(ZComponent component)
        {
            _component = component;
        }

        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var templateId = value as String;
            if (!String.IsNullOrEmpty(templateId))
            {
                var temp = (ControlTemplate) _component.FindResource(templateId);
                if (temp != null)
                {
                    return temp;
                }
            }
            return _component.Template;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        #endregion
    }

    #endregion
}